...and show them in menus when navigating the menu with the keyboard.
This is similar to what other platforms do, and reduces visual clutter.
There is a setting to control this. Most of the work on this patch was
done by Thomas Wood. See bug 588554.
gint width_chars;
gint max_width_chars;
gboolean full_size;
+ gboolean mnemonics_visible;
} GtkLabelPrivate;
/* Notes about the handling of links:
gpointer user_data);
+static void connect_mnemonics_visible_notify (GtkLabel *label);
+
+
/* For selectable labels: */
static void gtk_label_move_cursor (GtkLabel *label,
GtkMovementStep step,
label->mnemonic_widget = NULL;
label->mnemonic_window = NULL;
+ priv->mnemonics_visible = TRUE;
+
gtk_label_set_text (label, "");
}
}
if (label->mnemonic_keyval == GDK_VoidSymbol)
- goto done;
+ goto done;
+
+ connect_mnemonics_visible_notify (GTK_LABEL (widget));
toplevel = gtk_widget_get_toplevel (widget);
if (GTK_WIDGET_TOPLEVEL (toplevel))
g_list_free (list);
}
+static void
+mnemonics_visible_apply (GtkWidget *widget,
+ gboolean mnemonics_visible)
+{
+ GtkLabel *label;
+ GtkLabelPrivate *priv;
+
+ label = GTK_LABEL (widget);
+
+ if (!label->use_underline)
+ return;
+
+ priv = GTK_LABEL_GET_PRIVATE (label);
+
+ mnemonics_visible = mnemonics_visible != FALSE;
+
+ if (priv->mnemonics_visible != mnemonics_visible)
+ {
+ priv->mnemonics_visible = mnemonics_visible;
+
+ gtk_label_recalculate (label);
+ }
+}
+
+static void
+label_mnemonics_visible_traverse_container (GtkWidget *widget,
+ gpointer data)
+{
+ gboolean mnemonics_visible = GPOINTER_TO_INT (data);
+
+ _gtk_label_mnemonics_visible_apply_recursively (widget, mnemonics_visible);
+}
+
+void
+_gtk_label_mnemonics_visible_apply_recursively (GtkWidget *widget,
+ gboolean mnemonics_visible)
+{
+ if (GTK_IS_LABEL (widget))
+ mnemonics_visible_apply (widget, mnemonics_visible);
+ else if (GTK_IS_CONTAINER (widget))
+ gtk_container_forall (GTK_CONTAINER (widget),
+ label_mnemonics_visible_traverse_container,
+ GINT_TO_POINTER (mnemonics_visible));
+}
+
+static void
+label_mnemonics_visible_changed (GtkWindow *window,
+ GParamSpec *pspec,
+ gpointer data)
+{
+ gboolean mnemonics_visible;
+
+ g_object_get (window, "mnemonics-visible", &mnemonics_visible, NULL);
+
+ gtk_container_forall (GTK_CONTAINER (window),
+ label_mnemonics_visible_traverse_container,
+ GINT_TO_POINTER (mnemonics_visible));
+}
+
static void
gtk_label_screen_changed (GtkWidget *widget,
GdkScreen *old_screen)
gtk_label_set_pattern_internal (GtkLabel *label,
const gchar *pattern)
{
+ GtkLabelPrivate *priv = GTK_LABEL_GET_PRIVATE (label);
PangoAttrList *attrs;
gboolean enable_mnemonics;
"gtk-enable-mnemonics", &enable_mnemonics,
NULL);
- if (enable_mnemonics && pattern)
+ if (enable_mnemonics && priv->mnemonics_visible && pattern &&
+ GTK_WIDGET_IS_SENSITIVE (label))
attrs = gtk_label_pattern_to_attrs (label, pattern);
else
attrs = NULL;
return TRUE;
}
+static void
+connect_mnemonics_visible_notify (GtkLabel *label)
+{
+ GtkLabelPrivate *priv = GTK_LABEL_GET_PRIVATE (label);
+ GtkWidget *toplevel;
+ gboolean connected;
+
+ toplevel = gtk_widget_get_toplevel (GTK_WIDGET (label));
+
+ if (!GTK_IS_WINDOW (toplevel))
+ return;
+
+ /* always set up this widgets initial value */
+ priv->mnemonics_visible =
+ gtk_window_get_mnemonics_visible (GTK_WINDOW (toplevel));
+
+ connected =
+ GPOINTER_TO_INT (g_object_get_data (G_OBJECT (toplevel),
+ "gtk-label-mnemonics-visible-connected"));
+
+ if (!connected)
+ {
+ g_signal_connect (toplevel,
+ "notify::mnemonics-visible",
+ G_CALLBACK (label_mnemonics_visible_changed),
+ label);
+ g_object_set_data (G_OBJECT (toplevel),
+ "gtk-label-mnemonics-visible-connected",
+ GINT_TO_POINTER (1));
+ }
+}
+
static void
drag_begin_cb (GtkWidget *widget,
GdkDragContext *context,
if (window &&
window->default_widget != widget &&
!(widget == window->focus_widget &&
- (!window->default_widget || !GTK_WIDGET_SENSITIVE (window->default_widget))))
+ (!window->default_widget || !GTK_WIDGET_IS_SENSITIVE (window->default_widget))))
gtk_window_activate_default (window);
}
}
#endif /* GTK_DISABLE_DEPRECATED */
+/* private */
+
+void _gtk_label_mnemonics_visible_apply_recursively (GtkWidget *widget,
+ gboolean mnemonics_visible);
+
G_END_DECLS
#endif /* __GTK_LABEL_H__ */
#include "gtktooltip.h"
#include "gtkdebug.h"
#include "gtkalias.h"
+#include "gtkmenu.h"
+#include "gdk/gdkkeysyms.h"
#include "gdk/gdkprivate.h" /* for GDK_WINDOW_DESTROYED */
if (gtk_invoke_key_snoopers (grab_widget, event))
break;
}
+ /* catch alt press to enable auto-mnemonics */
+ if (event->key.keyval == GDK_Alt_L || event->key.keyval == GDK_Alt_R)
+ {
+ gboolean auto_mnemonics;
+
+ g_object_get (gtk_widget_get_settings (grab_widget),
+ "gtk-auto-mnemonics", &auto_mnemonics, NULL);
+
+ if (auto_mnemonics)
+ {
+ gboolean mnemonics_visible;
+ GtkWidget *window;
+
+ mnemonics_visible = (event->type == GDK_KEY_PRESS);
+
+ window = gtk_widget_get_toplevel (grab_widget);
+
+ if (GTK_IS_WINDOW (window))
+ gtk_window_set_mnemonics_visible (GTK_WINDOW (window), mnemonics_visible);
+ }
+ }
/* else fall through */
case GDK_MOTION_NOTIFY:
case GDK_BUTTON_RELEASE:
if (xgrab_shell == widget)
popup_grab_on_window (widget->window, activate_time, grab_keyboard); /* Should always succeed */
gtk_grab_add (GTK_WIDGET (menu));
+
+ if (parent_menu_shell)
+ {
+ gboolean keyboard_mode;
+
+ keyboard_mode = _gtk_menu_shell_get_keyboard_mode (GTK_MENU_SHELL (parent_menu_shell));
+ _gtk_menu_shell_set_keyboard_mode (menu_shell, keyboard_mode);
+ }
+ else if (menu_shell->button == 0) /* a keynav-activated context menu */
+ _gtk_menu_shell_set_keyboard_mode (menu_shell, TRUE);
+
+ _gtk_menu_shell_update_mnemonics (menu_shell);
}
void
{
GtkMenuShell *menu_shell = GTK_MENU_SHELL (menubars->data);
+ _gtk_menu_shell_set_keyboard_mode (menu_shell, TRUE);
_gtk_menu_shell_activate (menu_shell);
gtk_menu_shell_select_first (menu_shell, FALSE);
gtk_menu_item_mnemonic_activate (GtkWidget *widget,
gboolean group_cycling)
{
+ if (GTK_IS_MENU_SHELL (widget->parent))
+ _gtk_menu_shell_set_keyboard_mode (GTK_MENU_SHELL (widget->parent), TRUE);
+
if (group_cycling &&
widget->parent &&
GTK_IS_MENU_SHELL (widget->parent) &&
#include "gdk/gdkkeysyms.h"
#include "gtkbindings.h"
#include "gtkkeyhash.h"
+#include "gtklabel.h"
#include "gtkmain.h"
#include "gtkmarshalers.h"
#include "gtkmenu.h"
return TRUE;
}
+void
+_gtk_menu_shell_set_keyboard_mode (GtkMenuShell *menu_shell,
+ gboolean keyboard_mode)
+{
+ menu_shell->keyboard_mode = keyboard_mode;
+}
+
+gboolean
+_gtk_menu_shell_get_keyboard_mode (GtkMenuShell *menu_shell)
+{
+ return menu_shell->keyboard_mode;
+}
+
+void
+_gtk_menu_shell_update_mnemonics (GtkMenuShell *menu_shell)
+{
+ GtkMenuShell *target;
+ gboolean auto_mnemonics;
+ gboolean found;
+ gboolean mnemonics_visible;
+
+ g_object_get (gtk_widget_get_settings (GTK_WIDGET (menu_shell)),
+ "gtk-auto-mnemonics", &auto_mnemonics, NULL);
+
+ if (!auto_mnemonics)
+ return;
+
+ target = menu_shell;
+ found = FALSE;
+ while (target)
+ {
+ /* The idea with keyboard mode is that once you start using
+ * the keyboard to navigate the menus, we show mnemonics
+ * until the menu navigation is over. To that end, we spread
+ * the keyboard mode upwards in the menu hierarchy here.
+ * Also see gtk_menu_popup, where we inherit it downwards.
+ */
+ if (menu_shell->keyboard_mode)
+ target->keyboard_mode = TRUE;
+
+ /* While navigating menus, the first parent menu with an active
+ * item is the one where mnemonics are effective, as can be seen
+ * in gtk_menu_shell_key_press below.
+ * We also show mnemonics in context menus. The grab condition is
+ * necessary to ensure we remove underlines from menu bars when
+ * dismissing menus.
+ */
+ mnemonics_visible = target->keyboard_mode &&
+ ((target->active_menu_item && !found) ||
+ (target == menu_shell &&
+ !target->parent_menu_shell &&
+ gtk_widget_has_grab (target)));
+
+ /* While menus are up, only show underlines inside the menubar,
+ * not in the entire window.
+ */
+ if (GTK_IS_MENU_BAR (target))
+ _gtk_label_mnemonics_visible_apply_recursively (GTK_WIDGET (target),
+ mnemonics_visible);
+ else
+ gtk_window_set_mnemonics_visible (GTK_WINDOW (gtk_widget_get_toplevel (target)),
+ mnemonics_visible);
+
+ if (target->active_menu_item)
+ found = TRUE;
+
+ target = GTK_MENU_SHELL (target->parent_menu_shell);
+ }
+}
+
static gint
gtk_menu_shell_key_press (GtkWidget *widget,
GdkEventKey *event)
GtkMenuShell *menu_shell = GTK_MENU_SHELL (widget);
gboolean enable_mnemonics;
+ menu_shell->keyboard_mode = TRUE;
+
if (!menu_shell->active_menu_item && menu_shell->parent_menu_shell)
return gtk_widget_event (menu_shell->parent_menu_shell, (GdkEvent *)event);
-
+
if (gtk_bindings_activate_event (GTK_OBJECT (widget), event))
return TRUE;
if (menu_shell->have_xgrab)
{
GdkDisplay *display = gtk_widget_get_display (GTK_WIDGET (menu_shell));
-
+
menu_shell->have_xgrab = FALSE;
gdk_display_pointer_ungrab (display, GDK_CURRENT_TIME);
gdk_display_keyboard_ungrab (display, GDK_CURRENT_TIME);
}
+
+ menu_shell->keyboard_mode = FALSE;
+
+ _gtk_menu_shell_update_mnemonics (menu_shell);
}
}
GTK_MENU_SHELL_GET_CLASS (menu_shell)->submenu_placement);
gtk_menu_item_select (GTK_MENU_ITEM (menu_shell->active_menu_item));
+ _gtk_menu_shell_update_mnemonics (menu_shell);
+
/* This allows the bizarre radio buttons-with-submenus-display-history
* behavior
*/
{
gtk_menu_item_deselect (GTK_MENU_ITEM (menu_shell->active_menu_item));
menu_shell->active_menu_item = NULL;
+ _gtk_menu_shell_update_mnemonics (menu_shell);
}
}
* menu.
*/
_gtk_menu_item_popdown_submenu (menu_shell->active_menu_item);
+ _gtk_menu_shell_update_mnemonics (menu_shell);
}
else if (parent_menu_shell)
{
{
/* close menu when returning from submenu. */
_gtk_menu_item_popdown_submenu (GTK_MENU (menu_shell)->parent_menu_item);
+ _gtk_menu_shell_update_mnemonics (parent_menu_shell);
break;
}
guint GSEAL (ignore_leave) : 1; /* unused */
guint GSEAL (menu_flag) : 1; /* unused */
guint GSEAL (ignore_enter) : 1;
+ guint GSEAL (keyboard_mode) : 1;
};
struct _GtkMenuShellClass
void gtk_menu_shell_set_take_focus (GtkMenuShell *menu_shell,
gboolean take_focus);
+void _gtk_menu_shell_update_mnemonics (GtkMenuShell *menu_shell);
+void _gtk_menu_shell_set_keyboard_mode (GtkMenuShell *menu_shell,
+ gboolean keyboard_mode);
+gboolean _gtk_menu_shell_get_keyboard_mode (GtkMenuShell *menu_shell);
+
G_END_DECLS
#endif /* __GTK_MENU_SHELL_H__ */
PROP_ENABLE_EVENT_SOUNDS,
PROP_ENABLE_TOOLTIPS,
PROP_TOOLBAR_STYLE,
- PROP_TOOLBAR_ICON_SIZE
+ PROP_TOOLBAR_ICON_SIZE,
+ PROP_AUTO_MNEMONICS
};
GTK_PARAM_READWRITE),
gtk_rc_property_parse_enum);
g_assert (result == PROP_TOOLBAR_ICON_SIZE);
+
+ /**
+ * GtkSettings:gtk-auto-mnemonics:
+ *
+ * Whether mnemonics should be automatically shown and hidden when the user
+ * presses the mnemonic activator.
+ *
+ * Since: 2.20
+ */
+ result = settings_install_property_parser (class,
+ g_param_spec_boolean ("gtk-auto-mnemonics",
+ P_("Auto Mnemonics"),
+ P_("Whether mnemonics should be automatically shown and hidden when the user presses the mnemonic activator."),
+ FALSE,
+ GTK_PARAM_READWRITE),
+ NULL);
+ g_assert (result == PROP_AUTO_MNEMONICS);
}
static void
/* Writeonly properties */
PROP_STARTUP_ID,
+ PROP_MNEMONICS_VISIBLE,
+
LAST_ARG
};
guint opacity_set : 1;
guint builder_visible : 1;
+ guint mnemonics_visible : 1;
+ guint mnemonics_visible_set : 1;
+
GdkWindowTypeHint type_hint;
gdouble opacity;
static void gtk_window_check_resize (GtkContainer *container);
static gint gtk_window_focus (GtkWidget *widget,
GtkDirectionType direction);
+static void gtk_window_grab_notify (GtkWidget *widget,
+ gboolean was_grabbed);
static void gtk_window_real_set_focus (GtkWindow *window,
GtkWidget *focus);
widget_class->focus_out_event = gtk_window_focus_out_event;
widget_class->client_event = gtk_window_client_event;
widget_class->focus = gtk_window_focus;
-
widget_class->expose_event = gtk_window_expose;
-
+ widget_class->grab_notify = gtk_window_grab_notify;
+
container_class->check_resize = gtk_window_check_resize;
klass->set_focus = gtk_window_real_set_focus;
P_("Icon for this window"),
GDK_TYPE_PIXBUF,
GTK_PARAM_READWRITE));
+ g_object_class_install_property (gobject_class,
+ PROP_MNEMONICS_VISIBLE,
+ g_param_spec_boolean ("mnemonics-visible",
+ P_("Mnemonics Visible"),
+ P_("Whether mnemonics are currently visible in this window"),
+ TRUE,
+ GTK_PARAM_READWRITE));
/**
* GtkWindow:icon-name:
priv->type_hint = GDK_WINDOW_TYPE_HINT_NORMAL;
priv->opacity = 1.0;
priv->startup_id = NULL;
+ priv->mnemonics_visible = TRUE;
colormap = _gtk_widget_peek_colormap ();
if (colormap)
GParamSpec *pspec)
{
GtkWindow *window;
+ GtkWindowPrivate *priv;
window = GTK_WINDOW (object);
+ priv = GTK_WINDOW_GET_PRIVATE (window);
+
switch (prop_id)
{
case PROP_TYPE:
case PROP_OPACITY:
gtk_window_set_opacity (window, g_value_get_double (value));
break;
+ case PROP_MNEMONICS_VISIBLE:
+ gtk_window_set_mnemonics_visible (window, g_value_get_boolean (value));
+ break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
case PROP_OPACITY:
g_value_set_double (value, gtk_window_get_opacity (window));
break;
+ case PROP_MNEMONICS_VISIBLE:
+ g_value_set_boolean (value, priv->mnemonics_visible);
+ break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
GtkWindow *window = GTK_WINDOW (widget);
GtkWindowPrivate *priv = GTK_WINDOW_GET_PRIVATE (window);
GdkWindow *toplevel;
+ gboolean auto_mnemonics;
GTK_WIDGET_SET_FLAGS (widget, GTK_MAPPED);
gdk_notify_startup_complete ();
}
}
+
+ /* if auto-mnemonics is enabled and mnemonics visible is not already set
+ * (as in the case of popup menus), then hide mnemonics initially
+ */
+ g_object_get (gtk_widget_get_settings (widget), "gtk-auto-mnemonics",
+ &auto_mnemonics, NULL);
+ if (auto_mnemonics && !priv->mnemonics_visible_set)
+ gtk_window_set_mnemonics_visible (window, FALSE);
}
static gboolean
GdkEventFocus *event)
{
GtkWindow *window = GTK_WINDOW (widget);
+ gboolean auto_mnemonics;
_gtk_window_set_has_toplevel_focus (window, FALSE);
_gtk_window_set_is_active (window, FALSE);
+ /* set the mnemonic-visible property to false */
+ g_object_get (gtk_widget_get_settings (widget),
+ "gtk-auto-mnemonics", &auto_mnemonics, NULL);
+ if (auto_mnemonics)
+ gtk_window_set_mnemonics_visible (window, FALSE);
+
+
return FALSE;
}
return window->type;
}
+gboolean
+gtk_window_get_mnemonics_visible (GtkWindow *window)
+{
+ GtkWindowPrivate *priv;
+
+ g_return_val_if_fail (GTK_IS_WINDOW (window), FALSE);
+
+ priv = GTK_WINDOW_GET_PRIVATE (window);
+
+ return priv->mnemonics_visible;
+}
+
+void
+gtk_window_set_mnemonics_visible (GtkWindow *window,
+ gboolean setting)
+{
+ GtkWindowPrivate *priv;
+
+ g_return_if_fail (GTK_IS_WINDOW (window));
+
+ priv = GTK_WINDOW_GET_PRIVATE (window);
+
+ setting = setting != FALSE;
+
+ if (priv->mnemonics_visible != setting)
+ {
+ priv->mnemonics_visible = setting;
+ g_object_notify (G_OBJECT (window), "mnemonics-visible");
+ }
+
+ priv->mnemonics_visible_set = TRUE;
+}
+
+static void
+gtk_window_grab_notify (GtkWidget *widget,
+ gboolean was_grabbed)
+{
+ gboolean auto_mnemonics;
+
+ if (was_grabbed)
+ return;
+
+ g_object_get (gtk_widget_get_settings (widget), "gtk-auto-mnemonics",
+ &auto_mnemonics, NULL);
+
+ if (auto_mnemonics)
+ gtk_window_set_mnemonics_visible (GTK_WINDOW (widget), FALSE);
+}
+
#if defined (G_OS_WIN32) && !defined (_WIN64)
#undef gtk_window_set_icon_from_file
void gtk_window_set_destroy_with_parent (GtkWindow *window,
gboolean setting);
gboolean gtk_window_get_destroy_with_parent (GtkWindow *window);
+void gtk_window_set_mnemonics_visible (GtkWindow *window,
+ gboolean setting);
+gboolean gtk_window_get_mnemonics_visible (GtkWindow *window);
void gtk_window_set_resizable (GtkWindow *window,
gboolean resizable);